package lumis.portal.serviceinterface.serialization;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;

import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.PortalContext.RelativePathType;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.progress.ProgressConfig.Type;
import lumis.portal.serialization.BaseSerializationContext;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.serialization.FileCopyTransactionObserver;
import lumis.portal.serialization.SerializationContext;
import lumis.portal.serialization.SerializationException;
import lumis.portal.serialization.SerializationMessageConfigNode;
import lumis.portal.serialization.operation.ConfigDependencyAnalyzer;
import lumis.portal.serialization.operation.ConfigForeignKey;
import lumis.portal.serialization.operation.IConfigPersister;
import lumis.portal.serialization.persistency.IDirectory;
import lumis.portal.serviceinterface.ServiceInterfaceConfig;
import lumis.portal.serviceinterface.style.ServiceInterfaceStyleConfig;
import lumis.portal.serviceinterface.style.serialization.ServiceInterfaceStyleSerializableConfig;
import lumis.util.FileUtil;
import lumis.util.PortalUtil;

/**
 * Manager for Service Interface serialization
 *
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 4.0.7
 */
public class ServiceInterfaceSerializationManager implements IServiceInterfaceSerializationManager
{
	protected final List<ConfigForeignKey> styleForeignKeys = Arrays.asList(new ConfigForeignKey[] {
			new ConfigForeignKey("serviceInterfaceId", null, false, true, false, ManagerFactory.getServiceInterfaceManager())
	});

	private final String configAlias = "serviceInterfaceStyle";
	
	// DESERIALIZATION
	
	public void deserializeStyle(DeserializationContext deserializationContext, ServiceInterfaceStyleSerializableConfig serviceInterfaceStyleSerializableConfig) throws PortalException
	{
		deserializationContext.logDebug("Deserializing service interface style: serviceInterfaceId:" + serviceInterfaceStyleSerializableConfig.getServiceInterfaceId() + " serviceInterfaceStyleId:" + serviceInterfaceStyleSerializableConfig.getId());

		// deserialize config
		ServiceInterfaceStyleConfig serviceInterfaceStyleConfig = ManagerFactoryInternal.getSerializationManager2().deserializeObject(ServiceInterfaceStyleConfig.class, deserializationContext.getDirectory(), getStyleConfigPath(deserializationContext, serviceInterfaceStyleSerializableConfig.getServiceInterfaceId(), serviceInterfaceStyleSerializableConfig.getId()), configAlias, false);
		
		// analyze dependencies and persist config
		ConfigDependencyAnalyzer<ServiceInterfaceStyleConfig> dependencyAnalyzer = new ConfigDependencyAnalyzer<ServiceInterfaceStyleConfig>(deserializationContext, styleForeignKeys, serviceInterfaceStyleConfig);
		dependencyAnalyzer.persist(getStyleConfigPersister(), false);
	}

	private IConfigPersister getStyleConfigPersister()
	{
		return new IConfigPersister()
		{
			public boolean persistConfig(DeserializationContext deserializationContext, Object config, boolean firstPersistence) throws PortalException
			{
				ServiceInterfaceStyleConfig serviceInterfaceStyleConfig = (ServiceInterfaceStyleConfig)config;
				
				// determine file paths
				String sourceFilePath = getStyleFilePath(deserializationContext, serviceInterfaceStyleConfig.getServiceInterfaceId(), serviceInterfaceStyleConfig.getId());
				String destinationFileRelativePath = serviceInterfaceStyleConfig.getFilePath();
				String destinationFileAbsolutePath = RelativePathType.Def.getAbsolutePath(destinationFileRelativePath);
				File destinationFile = new File(destinationFileAbsolutePath);
				IDirectory directory = deserializationContext.getDirectory();

				// add config if it not already exists
				boolean deserializeStyleFile = false;
				ServiceInterfaceStyleConfig existingServiceInterfaceStyleConfig = null;
				try
				{
					existingServiceInterfaceStyleConfig = ManagerFactory.getServiceInterfaceManager().getStyle(deserializationContext.getSessionConfig(), serviceInterfaceStyleConfig.getId(), serviceInterfaceStyleConfig.getServiceInterfaceId(), deserializationContext.getTransaction());
				}
				catch (PortalObjectNotFoundException e1)
				{
				}
				if (existingServiceInterfaceStyleConfig == null)
				{
					if (!destinationFile.exists())
					{
						deserializationContext.logDebug(" Style does not exist. Adding:", serviceInterfaceStyleConfig);
						deserializeStyleFile = true;
					}
					else // conflict
					{
						deserializationContext.logDebug(" Style does not exist, but its file exists. Resolving file conflict:", serviceInterfaceStyleConfig);
						switch(deserializationContext.getExistingStyleAction())
						{
							case ABORT:
								deserializationContext.sendProgressNotification("STR_STYLE_FILE_ALREADY_EXISTS;" + destinationFileRelativePath, Type.ERROR, null);
								serviceInterfaceStyleConfig.setFilePath(PortalUtil.generateNewGuid());
								deserializationContext.setMustRollBack();
								break;
							case SKIP:
								deserializationContext.sendProgressNotification("STR_MAINTAINING_STYLE;" + destinationFileRelativePath, Type.WARNING, null);
								break;
							case UPDATE:
								deserializationContext.sendProgressNotification("STR_UPDATING_STYLE_FILE;" + destinationFileRelativePath, Type.WARNING, null);
								deserializeStyleFile = true;
								break;
						}
						
					}
					ConfigDependencyAnalyzer<ServiceInterfaceStyleConfig> dependencyAnalyzer = new ConfigDependencyAnalyzer<ServiceInterfaceStyleConfig>(deserializationContext, styleForeignKeys, serviceInterfaceStyleConfig);
					if (dependencyAnalyzer.foreignKeysExists())
					{
						ManagerFactory.getServiceInterfaceManager().addOrUpdateStyle(deserializationContext.getSessionConfig(), serviceInterfaceStyleConfig, deserializationContext.getTransaction());
					}
				}
				else
				{
					try
					{
						deserializationContext.logDebug(" Style already exists: Resolving conflict");

						// a file conflict exists when the configs or their referenced file contents are different
						// (absent destination file implies content difference)
						// when there is no conflict, no update is needed
						if (!serviceInterfaceStyleConfig.equals(existingServiceInterfaceStyleConfig) || !compareFiles(directory, sourceFilePath, destinationFile)) // conflict
						{
							String existingStyleName = existingServiceInterfaceStyleConfig.getName();
							String existingStylePath = existingServiceInterfaceStyleConfig.getFilePath();
							String localizedExistingStyleName = ManagerFactory.getLocalizationManager().localize(existingStyleName, ManagerFactory.getServiceInterfaceManager().getServiceInterfaceConfig(serviceInterfaceStyleConfig.getServiceInterfaceId(), deserializationContext.getTransaction()).getResource());
							String existingStyleFinalName = "'" + localizedExistingStyleName + "' (" + existingStylePath + ")";
							String newStyleName = serviceInterfaceStyleConfig.getName();
							String newStylePath = serviceInterfaceStyleConfig.getFilePath();
							String localizedNewStyleName = ManagerFactory.getLocalizationManager().localize(newStyleName, ManagerFactory.getServiceInterfaceManager().getServiceInterfaceConfig(serviceInterfaceStyleConfig.getServiceInterfaceId(), deserializationContext.getTransaction()).getResource());
							String newStyleFinalName = "'" + localizedNewStyleName + "' (" + newStylePath + ")";
							String message;
							switch(deserializationContext.getExistingStyleAction())
							{
								case ABORT:
									if (newStyleName.equals(existingStyleName) && newStylePath.equals(existingStylePath))
										message = "STR_STYLE_ALREADY_EXISTS;" + existingStyleFinalName;
									else
										message = "STR_STYLE_CONFLICTS_TO;" + existingStyleFinalName + ";" + newStyleFinalName;
									deserializationContext.sendProgressNotification(message, Type.ERROR, null);
									deserializationContext.setMustRollBack();
									break;
								case UPDATE:
									if (newStyleName.equals(existingStyleName) && newStylePath.equals(existingStylePath))
										message = "STR_UPDATING_STYLE;" + existingStyleFinalName;
									else
										message = "STR_UPDATING_STYLE_TO;" + existingStyleFinalName + ";" + newStyleFinalName;
									deserializationContext.sendProgressNotification(message, Type.WARNING, null);
									ManagerFactory.getServiceInterfaceManager().addOrUpdateStyle(deserializationContext.getSessionConfig(), serviceInterfaceStyleConfig, deserializationContext.getTransaction());
									deserializeStyleFile = true;
									break;
								case SKIP:
									if (newStyleName.equals(existingStyleName) && newStylePath.equals(existingStylePath))
										message = "STR_MAINTAINING_STYLE;" + existingStyleFinalName;
									else
										message = "STR_MAINTAINING_STYLE_IGNORING;" + existingStyleFinalName + ";" + newStyleFinalName;
									deserializationContext.sendProgressNotification(message, Type.WARNING, null);
									break;
							}
						}
					}
					catch (IOException e)
					{
						throw new SerializationException("STR_ERROR_ANALYZING_STYLE;" + ManagerFactory.getLocalizationManager().localize(serviceInterfaceStyleConfig.getName(), ManagerFactory.getServiceInterfaceManager().getServiceInterfaceConfig(serviceInterfaceStyleConfig.getServiceInterfaceId(), deserializationContext.getTransaction()).getResource()) + ";" + destinationFileRelativePath, e);
					}
				}
				boolean sourceFileExists;
				try
				{
					sourceFileExists = directory.exists(sourceFilePath);
				}
				catch (IOException e)
				{
					throw new SerializationException("STR_ERROR_ANALYZING_STYLE;" + ManagerFactory.getLocalizationManager().localize(serviceInterfaceStyleConfig.getName(), ManagerFactory.getServiceInterfaceManager().getServiceInterfaceConfig(serviceInterfaceStyleConfig.getServiceInterfaceId(), deserializationContext.getTransaction()).getResource()) + ";" + destinationFileRelativePath, e);
				}
				if (!sourceFileExists)
					deserializationContext.sendProgressNotification("STR_SOURCE_STYLE_NOT_FOUND;" + sourceFilePath, Type.WARNING, null);
				// deserialize style file
				if (deserializeStyleFile && sourceFileExists)
				{
					deserializationContext.logDebug(" Adding FileCopyTransactionObserver: sourcePath:DATA\\" + sourceFilePath + " destinationPath:WWW\\" + destinationFileRelativePath);
					FileCopyTransactionObserver fileCopyTransactionObserver = new FileCopyTransactionObserver(deserializationContext.getSessionConfig(), sourceFilePath, directory, destinationFileRelativePath, RelativePathType.Def, deserializationContext.getTransaction());
					deserializationContext.getTransaction().addObserver(fileCopyTransactionObserver);
				}
				return true;
			}
		};
	}

	private boolean compareFiles(IDirectory sourceDirectory, String sourceFilePath, File destinationFile) throws IOException
	{
		boolean sourceFileExists = sourceDirectory.exists(sourceFilePath);
		if (sourceFileExists != destinationFile.exists()) // there are differences 
			return false;
		if (!sourceFileExists) // neither source or destination exist
			return true;
		InputStream sourceStream = sourceDirectory.getInputStream(sourceFilePath);
		try
		{
			InputStream in2 = new FileInputStream(destinationFile);
			try
			{
				return FileUtil.compareStream(sourceStream, in2);
			}
			finally
			{
				in2.close();				
			}
		}
		finally
		{
			sourceStream.close();
		}
	}

	// SERIALIZATION

	public ServiceInterfaceStyleSerializableConfig serializeStyle(SerializationContext serializationContext, String serviceInterfaceStyleId, String serviceInterfaceId) throws PortalException
	{
		// get config
		ServiceInterfaceStyleConfig serviceInterfaceStyleConfig = ManagerFactory.getServiceInterfaceManager().getStyle(serializationContext.getSessionConfig(), serviceInterfaceStyleId, serviceInterfaceId, serializationContext.getTransaction());

		// serialize config
		ManagerFactoryInternal.getSerializationManager2().serializeObject(serviceInterfaceStyleConfig, serializationContext.getDirectory(), getStyleConfigPath(serializationContext, serviceInterfaceId, serviceInterfaceStyleConfig.getId()), configAlias);

		// serialize file
		serializeStyleFile(serializationContext, serviceInterfaceStyleConfig);

		// build structure object
		return createStyleSerializableConfig(serviceInterfaceStyleConfig);
	}

	private void serializeStyleFile(SerializationContext serializationContext, ServiceInterfaceStyleConfig serviceInterfaceStyleConfig) throws SerializationException
	{
		File srcFile = new File(RelativePathType.Def.getAbsolutePath(serviceInterfaceStyleConfig.getFilePath()));
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try
		{
			inputStream = new FileInputStream(srcFile);
			outputStream = serializationContext.getDirectory().getOutputStream(getStyleFilePath(serializationContext, serviceInterfaceStyleConfig.getServiceInterfaceId(), serviceInterfaceStyleConfig.getId())); 
			FileUtil.copyStream(inputStream, outputStream);
		}
		catch (IOException e)
		{
			throw new SerializationException("STR_ERROR_SERIALIZING_STYLE_FILE;" + serviceInterfaceStyleConfig.getFilePath(), e);
		}
		finally
		{
			try
			{
				if (inputStream != null)
					inputStream.close();
			}
			catch (IOException e)
			{
				serializationContext.logDebug("Error closing file copy inputStream", e);
			}
			try
			{
				if (outputStream != null)
					outputStream.close();
			}
			catch (IOException e)
			{
				serializationContext.logDebug("Error closing file copy outputStream", e);
			}
		}
	}

	private ServiceInterfaceStyleSerializableConfig createStyleSerializableConfig(ServiceInterfaceStyleConfig serviceInterfaceStyleConfig)
	{
		ServiceInterfaceStyleSerializableConfig serviceInterfaceStyleSerializableConfig = new ServiceInterfaceStyleSerializableConfig();
		serviceInterfaceStyleSerializableConfig.setId(serviceInterfaceStyleConfig.getId());
		serviceInterfaceStyleSerializableConfig.setServiceInterfaceId(serviceInterfaceStyleConfig.getServiceInterfaceId());
		return serviceInterfaceStyleSerializableConfig;
	}

	// GENERAL
	
	public void setupSerializationContext(BaseSerializationContext serializationContext)
	{
		serializationContext.getSerializationSettings().getClassAliases().put(ServiceInterfaceStyleSerializableConfig.class, "serviceInterfaceStyle");
	}

	private String getStyleConfigPath(BaseSerializationContext deserializationContext, String serviceInterfaceId, String serviceInterfaceStyleId)
	{
		return "serviceInterfaceStyles/" + serviceInterfaceId + "/" + serviceInterfaceStyleId + ".xml";
	}

	private String getStyleFilePath(BaseSerializationContext deserializationContext, String serviceInterfaceId, String serviceInterfaceStyleId)
	{
		return "serviceInterfaceStyles/" + serviceInterfaceId + "/" + serviceInterfaceStyleId + ".xsl";
	}

	public SerializationMessageConfigNode getSerializationMessageConfigNode(BaseSerializationContext baseSerializationContext, ServiceInterfaceConfig serviceInterfaceConfig) throws PortalException
	{
		ServiceInterfaceSerializationMessageConfig serviceInterfaceSerializationMessageConfig = new ServiceInterfaceSerializationMessageConfig();
		serviceInterfaceSerializationMessageConfig.setId(serviceInterfaceConfig.getId());
		serviceInterfaceSerializationMessageConfig.setTitle(serviceInterfaceConfig.getTitle());
		String serviceInterfaceName = serviceInterfaceConfig.getName();
		// localize service name at serialization time, using current locale, because at deserialization time, 
		// if the service does not exist in destination portal, there will not be any available resource  
		serviceInterfaceName = ManagerFactory.getLocalizationManager().localize(serviceInterfaceName, serviceInterfaceConfig.getResource(), baseSerializationContext.getSessionConfig().getLocale());
		serviceInterfaceSerializationMessageConfig.setName(serviceInterfaceName);

		SerializationMessageConfigNode serializationMessageConfigNode = new SerializationMessageConfigNode();
		serializationMessageConfigNode.setSerializationMessageConfig(serviceInterfaceSerializationMessageConfig);
		
		return serializationMessageConfigNode;
	}

}
